1 /*
2 * Copyright (C) 2007 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.io;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.io.FileWriteMode.APPEND;
22
23 import com.google.common.annotations.Beta;
24 import com.google.common.base.Charsets;
25 import com.google.common.base.Joiner;
26 import com.google.common.base.Predicate;
27 import com.google.common.base.Splitter;
28 import com.google.common.collect.ImmutableSet;
29 import com.google.common.collect.Lists;
30 import com.google.common.collect.TreeTraverser;
31 import com.google.common.hash.HashCode;
32 import com.google.common.hash.HashFunction;
33
34 import java.io.BufferedReader;
35 import java.io.BufferedWriter;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileNotFoundException;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStream;
44 import java.io.OutputStreamWriter;
45 import java.io.RandomAccessFile;
46 import java.nio.MappedByteBuffer;
47 import java.nio.channels.FileChannel;
48 import java.nio.channels.FileChannel.MapMode;
49 import java.nio.charset.Charset;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.List;
54
55 /**
56 * Provides utility methods for working with files.
57 *
58 * <p>All method parameters must be non-null unless documented otherwise.
59 *
60 * @author Chris Nokleberg
61 * @author Colin Decker
62 * @since 1.0
63 */
64 @Beta
65 public final class Files {
66
67 /** Maximum loop count when creating temp directories. */
68 private static final int TEMP_DIR_ATTEMPTS = 10000;
69
70 private Files() {}
71
72 /**
73 * Returns a buffered reader that reads from a file using the given
74 * character set.
75 *
76 * @param file the file to read from
77 * @param charset the charset used to decode the input stream; see {@link
78 * Charsets} for helpful predefined constants
79 * @return the buffered reader
80 */
81 public static BufferedReader newReader(File file, Charset charset)
82 throws FileNotFoundException {
83 checkNotNull(file);
84 checkNotNull(charset);
85 return new BufferedReader(
86 new InputStreamReader(new FileInputStream(file), charset));
87 }
88
89 /**
90 * Returns a buffered writer that writes to a file using the given
91 * character set.
92 *
93 * @param file the file to write to
94 * @param charset the charset used to encode the output stream; see {@link
95 * Charsets} for helpful predefined constants
96 * @return the buffered writer
97 */
98 public static BufferedWriter newWriter(File file, Charset charset)
99 throws FileNotFoundException {
100 checkNotNull(file);
101 checkNotNull(charset);
102 return new BufferedWriter(
103 new OutputStreamWriter(new FileOutputStream(file), charset));
104 }
105
106 /**
107 * Returns a new {@link ByteSource} for reading bytes from the given file.
108 *
109 * @since 14.0
110 */
111 public static ByteSource asByteSource(File file) {
112 return new FileByteSource(file);
113 }
114
115 private static final class FileByteSource extends ByteSource {
116
117 private final File file;
118
119 private FileByteSource(File file) {
120 this.file = checkNotNull(file);
121 }
122
123 @Override
124 public FileInputStream openStream() throws IOException {
125 return new FileInputStream(file);
126 }
127
128 @Override
129 public long size() throws IOException {
130 if (!file.isFile()) {
131 throw new FileNotFoundException(file.toString());
132 }
133 return file.length();
134 }
135
136 @Override
137 public byte[] read() throws IOException {
138 Closer closer = Closer.create();
139 try {
140 FileInputStream in = closer.register(openStream());
141 return readFile(in, in.getChannel().size());
142 } catch (Throwable e) {
143 throw closer.rethrow(e);
144 } finally {
145 closer.close();
146 }
147 }
148
149 @Override
150 public String toString() {
151 return "Files.asByteSource(" + file + ")";
152 }
153 }
154
155 /**
156 * Reads a file of the given expected size from the given input stream, if
157 * it will fit into a byte array. This method handles the case where the file
158 * size changes between when the size is read and when the contents are read
159 * from the stream.
160 */
161 static byte[] readFile(
162 InputStream in, long expectedSize) throws IOException {
163 if (expectedSize > Integer.MAX_VALUE) {
164 throw new OutOfMemoryError("file is too large to fit in a byte array: "
165 + expectedSize + " bytes");
166 }
167
168 // some special files may return size 0 but have content, so read
169 // the file normally in that case
170 return expectedSize == 0
171 ? ByteStreams.toByteArray(in)
172 : ByteStreams.toByteArray(in, (int) expectedSize);
173 }
174
175 /**
176 * Returns a new {@link ByteSink} for writing bytes to the given file. The
177 * given {@code modes} control how the file is opened for writing. When no
178 * mode is provided, the file will be truncated before writing. When the
179 * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
180 * append to the end of the file without truncating it.
181 *
182 * @since 14.0
183 */
184 public static ByteSink asByteSink(File file, FileWriteMode... modes) {
185 return new FileByteSink(file, modes);
186 }
187
188 private static final class FileByteSink extends ByteSink {
189
190 private final File file;
191 private final ImmutableSet<FileWriteMode> modes;
192
193 private FileByteSink(File file, FileWriteMode... modes) {
194 this.file = checkNotNull(file);
195 this.modes = ImmutableSet.copyOf(modes);
196 }
197
198 @Override
199 public FileOutputStream openStream() throws IOException {
200 return new FileOutputStream(file, modes.contains(APPEND));
201 }
202
203 @Override
204 public String toString() {
205 return "Files.asByteSink(" + file + ", " + modes + ")";
206 }
207 }
208
209 /**
210 * Returns a new {@link CharSource} for reading character data from the given
211 * file using the given character set.
212 *
213 * @since 14.0
214 */
215 public static CharSource asCharSource(File file, Charset charset) {
216 return asByteSource(file).asCharSource(charset);
217 }
218
219 /**
220 * Returns a new {@link CharSink} for writing character data to the given
221 * file using the given character set. The given {@code modes} control how
222 * the file is opened for writing. When no mode is provided, the file
223 * will be truncated before writing. When the
224 * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
225 * append to the end of the file without truncating it.
226 *
227 * @since 14.0
228 */
229 public static CharSink asCharSink(File file, Charset charset,
230 FileWriteMode... modes) {
231 return asByteSink(file, modes).asCharSink(charset);
232 }
233
234 private static FileWriteMode[] modes(boolean append) {
235 return append
236 ? new FileWriteMode[]{ FileWriteMode.APPEND }
237 : new FileWriteMode[0];
238 }
239
240 /**
241 * Reads all bytes from a file into a byte array.
242 *
243 * @param file the file to read from
244 * @return a byte array containing all the bytes from file
245 * @throws IllegalArgumentException if the file is bigger than the largest
246 * possible byte array (2^31 - 1)
247 * @throws IOException if an I/O error occurs
248 */
249 public static byte[] toByteArray(File file) throws IOException {
250 return asByteSource(file).read();
251 }
252
253 /**
254 * Reads all characters from a file into a {@link String}, using the given
255 * character set.
256 *
257 * @param file the file to read from
258 * @param charset the charset used to decode the input stream; see {@link
259 * Charsets} for helpful predefined constants
260 * @return a string containing all the characters from the file
261 * @throws IOException if an I/O error occurs
262 */
263 public static String toString(File file, Charset charset) throws IOException {
264 return asCharSource(file, charset).read();
265 }
266
267 /**
268 * Overwrites a file with the contents of a byte array.
269 *
270 * @param from the bytes to write
271 * @param to the destination file
272 * @throws IOException if an I/O error occurs
273 */
274 public static void write(byte[] from, File to) throws IOException {
275 asByteSink(to).write(from);
276 }
277
278 /**
279 * Copies all bytes from a file to an output stream.
280 *
281 * @param from the source file
282 * @param to the output stream
283 * @throws IOException if an I/O error occurs
284 */
285 public static void copy(File from, OutputStream to) throws IOException {
286 asByteSource(from).copyTo(to);
287 }
288
289 /**
290 * Copies all the bytes from one file to another.
291 *
292 * <p><b>Warning:</b> If {@code to} represents an existing file, that file
293 * will be overwritten with the contents of {@code from}. If {@code to} and
294 * {@code from} refer to the <i>same</i> file, the contents of that file
295 * will be deleted.
296 *
297 * @param from the source file
298 * @param to the destination file
299 * @throws IOException if an I/O error occurs
300 * @throws IllegalArgumentException if {@code from.equals(to)}
301 */
302 public static void copy(File from, File to) throws IOException {
303 checkArgument(!from.equals(to),
304 "Source %s and destination %s must be different", from, to);
305 asByteSource(from).copyTo(asByteSink(to));
306 }
307
308 /**
309 * Writes a character sequence (such as a string) to a file using the given
310 * character set.
311 *
312 * @param from the character sequence to write
313 * @param to the destination file
314 * @param charset the charset used to encode the output stream; see {@link
315 * Charsets} for helpful predefined constants
316 * @throws IOException if an I/O error occurs
317 */
318 public static void write(CharSequence from, File to, Charset charset)
319 throws IOException {
320 asCharSink(to, charset).write(from);
321 }
322
323 /**
324 * Appends a character sequence (such as a string) to a file using the given
325 * character set.
326 *
327 * @param from the character sequence to append
328 * @param to the destination file
329 * @param charset the charset used to encode the output stream; see {@link
330 * Charsets} for helpful predefined constants
331 * @throws IOException if an I/O error occurs
332 */
333 public static void append(CharSequence from, File to, Charset charset)
334 throws IOException {
335 write(from, to, charset, true);
336 }
337
338 /**
339 * Private helper method. Writes a character sequence to a file,
340 * optionally appending.
341 *
342 * @param from the character sequence to append
343 * @param to the destination file
344 * @param charset the charset used to encode the output stream; see {@link
345 * Charsets} for helpful predefined constants
346 * @param append true to append, false to overwrite
347 * @throws IOException if an I/O error occurs
348 */
349 private static void write(CharSequence from, File to, Charset charset,
350 boolean append) throws IOException {
351 asCharSink(to, charset, modes(append)).write(from);
352 }
353
354 /**
355 * Copies all characters from a file to an appendable object,
356 * using the given character set.
357 *
358 * @param from the source file
359 * @param charset the charset used to decode the input stream; see {@link
360 * Charsets} for helpful predefined constants
361 * @param to the appendable object
362 * @throws IOException if an I/O error occurs
363 */
364 public static void copy(File from, Charset charset, Appendable to)
365 throws IOException {
366 asCharSource(from, charset).copyTo(to);
367 }
368
369 /**
370 * Returns true if the files contains the same bytes.
371 *
372 * @throws IOException if an I/O error occurs
373 */
374 public static boolean equal(File file1, File file2) throws IOException {
375 checkNotNull(file1);
376 checkNotNull(file2);
377 if (file1 == file2 || file1.equals(file2)) {
378 return true;
379 }
380
381 /*
382 * Some operating systems may return zero as the length for files
383 * denoting system-dependent entities such as devices or pipes, in
384 * which case we must fall back on comparing the bytes directly.
385 */
386 long len1 = file1.length();
387 long len2 = file2.length();
388 if (len1 != 0 && len2 != 0 && len1 != len2) {
389 return false;
390 }
391 return asByteSource(file1).contentEquals(asByteSource(file2));
392 }
393
394 /**
395 * Atomically creates a new directory somewhere beneath the system's
396 * temporary directory (as defined by the {@code java.io.tmpdir} system
397 * property), and returns its name.
398 *
399 * <p>Use this method instead of {@link File#createTempFile(String, String)}
400 * when you wish to create a directory, not a regular file. A common pitfall
401 * is to call {@code createTempFile}, delete the file and create a
402 * directory in its place, but this leads a race condition which can be
403 * exploited to create security vulnerabilities, especially when executable
404 * files are to be written into the directory.
405 *
406 * <p>This method assumes that the temporary volume is writable, has free
407 * inodes and free blocks, and that it will not be called thousands of times
408 * per second.
409 *
410 * @return the newly-created directory
411 * @throws IllegalStateException if the directory could not be created
412 */
413 public static File createTempDir() {
414 File baseDir = new File(System.getProperty("java.io.tmpdir"));
415 String baseName = System.currentTimeMillis() + "-";
416
417 for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
418 File tempDir = new File(baseDir, baseName + counter);
419 if (tempDir.mkdir()) {
420 return tempDir;
421 }
422 }
423 throw new IllegalStateException("Failed to create directory within "
424 + TEMP_DIR_ATTEMPTS + " attempts (tried "
425 + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
426 }
427
428 /**
429 * Creates an empty file or updates the last updated timestamp on the
430 * same as the unix command of the same name.
431 *
432 * @param file the file to create or update
433 * @throws IOException if an I/O error occurs
434 */
435 public static void touch(File file) throws IOException {
436 checkNotNull(file);
437 if (!file.createNewFile()
438 && !file.setLastModified(System.currentTimeMillis())) {
439 throw new IOException("Unable to update modification time of " + file);
440 }
441 }
442
443 /**
444 * Creates any necessary but nonexistent parent directories of the specified
445 * file. Note that if this operation fails it may have succeeded in creating
446 * some (but not all) of the necessary parent directories.
447 *
448 * @throws IOException if an I/O error occurs, or if any necessary but
449 * nonexistent parent directories of the specified file could not be
450 * created.
451 * @since 4.0
452 */
453 public static void createParentDirs(File file) throws IOException {
454 checkNotNull(file);
455 File parent = file.getCanonicalFile().getParentFile();
456 if (parent == null) {
457 /*
458 * The given directory is a filesystem root. All zero of its ancestors
459 * exist. This doesn't mean that the root itself exists -- consider x:\ on
460 * a Windows machine without such a drive -- or even that the caller can
461 * create it, but this method makes no such guarantees even for non-root
462 * files.
463 */
464 return;
465 }
466 parent.mkdirs();
467 if (!parent.isDirectory()) {
468 throw new IOException("Unable to create parent directories of " + file);
469 }
470 }
471
472 /**
473 * Moves a file from one path to another. This method can rename a file
474 * and/or move it to a different directory. In either case {@code to} must
475 * be the target path for the file itself; not just the new name for the
476 * file or the path to the new parent directory.
477 *
478 * @param from the source file
479 * @param to the destination file
480 * @throws IOException if an I/O error occurs
481 * @throws IllegalArgumentException if {@code from.equals(to)}
482 */
483 public static void move(File from, File to) throws IOException {
484 checkNotNull(from);
485 checkNotNull(to);
486 checkArgument(!from.equals(to),
487 "Source %s and destination %s must be different", from, to);
488
489 if (!from.renameTo(to)) {
490 copy(from, to);
491 if (!from.delete()) {
492 if (!to.delete()) {
493 throw new IOException("Unable to delete " + to);
494 }
495 throw new IOException("Unable to delete " + from);
496 }
497 }
498 }
499
500 /**
501 * Reads the first line from a file. The line does not include
502 * line-termination characters, but does include other leading and
503 * trailing whitespace.
504 *
505 * @param file the file to read from
506 * @param charset the charset used to decode the input stream; see {@link
507 * Charsets} for helpful predefined constants
508 * @return the first line, or null if the file is empty
509 * @throws IOException if an I/O error occurs
510 */
511 public static String readFirstLine(File file, Charset charset)
512 throws IOException {
513 return asCharSource(file, charset).readFirstLine();
514 }
515
516 /**
517 * Reads all of the lines from a file. The lines do not include
518 * line-termination characters, but do include other leading and
519 * trailing whitespace.
520 *
521 * <p>This method returns a mutable {@code List}. For an
522 * {@code ImmutableList}, use
523 * {@code Files.asCharSource(file, charset).readLines()}.
524 *
525 * @param file the file to read from
526 * @param charset the charset used to decode the input stream; see {@link
527 * Charsets} for helpful predefined constants
528 * @return a mutable {@link List} containing all the lines
529 * @throws IOException if an I/O error occurs
530 */
531 public static List<String> readLines(File file, Charset charset)
532 throws IOException {
533 // don't use asCharSource(file, charset).readLines() because that returns
534 // an immutable list, which would change the behavior of this method
535 return readLines(file, charset, new LineProcessor<List<String>>() {
536 final List<String> result = Lists.newArrayList();
537
538 @Override
539 public boolean processLine(String line) {
540 result.add(line);
541 return true;
542 }
543
544 @Override
545 public List<String> getResult() {
546 return result;
547 }
548 });
549 }
550
551 /**
552 * Streams lines from a {@link File}, stopping when our callback returns
553 * false, or we have read all of the lines.
554 *
555 * @param file the file to read from
556 * @param charset the charset used to decode the input stream; see {@link
557 * Charsets} for helpful predefined constants
558 * @param callback the {@link LineProcessor} to use to handle the lines
559 * @return the output of processing the lines
560 * @throws IOException if an I/O error occurs
561 */
562 public static <T> T readLines(File file, Charset charset,
563 LineProcessor<T> callback) throws IOException {
564 return asCharSource(file, charset).readLines(callback);
565 }
566
567 /**
568 * Process the bytes of a file.
569 *
570 * <p>(If this seems too complicated, maybe you're looking for
571 * {@link #toByteArray}.)
572 *
573 * @param file the file to read
574 * @param processor the object to which the bytes of the file are passed.
575 * @return the result of the byte processor
576 * @throws IOException if an I/O error occurs
577 */
578 public static <T> T readBytes(File file, ByteProcessor<T> processor)
579 throws IOException {
580 return asByteSource(file).read(processor);
581 }
582
583 /**
584 * Computes the hash code of the {@code file} using {@code hashFunction}.
585 *
586 * @param file the file to read
587 * @param hashFunction the hash function to use to hash the data
588 * @return the {@link HashCode} of all of the bytes in the file
589 * @throws IOException if an I/O error occurs
590 * @since 12.0
591 */
592 public static HashCode hash(File file, HashFunction hashFunction)
593 throws IOException {
594 return asByteSource(file).hash(hashFunction);
595 }
596
597 /**
598 * Fully maps a file read-only in to memory as per
599 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}.
600 *
601 * <p>Files are mapped from offset 0 to its length.
602 *
603 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
604 *
605 * @param file the file to map
606 * @return a read-only buffer reflecting {@code file}
607 * @throws FileNotFoundException if the {@code file} does not exist
608 * @throws IOException if an I/O error occurs
609 *
610 * @see FileChannel#map(MapMode, long, long)
611 * @since 2.0
612 */
613 public static MappedByteBuffer map(File file) throws IOException {
614 checkNotNull(file);
615 return map(file, MapMode.READ_ONLY);
616 }
617
618 /**
619 * Fully maps a file in to memory as per
620 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
621 * using the requested {@link MapMode}.
622 *
623 * <p>Files are mapped from offset 0 to its length.
624 *
625 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
626 *
627 * @param file the file to map
628 * @param mode the mode to use when mapping {@code file}
629 * @return a buffer reflecting {@code file}
630 * @throws FileNotFoundException if the {@code file} does not exist
631 * @throws IOException if an I/O error occurs
632 *
633 * @see FileChannel#map(MapMode, long, long)
634 * @since 2.0
635 */
636 public static MappedByteBuffer map(File file, MapMode mode)
637 throws IOException {
638 checkNotNull(file);
639 checkNotNull(mode);
640 if (!file.exists()) {
641 throw new FileNotFoundException(file.toString());
642 }
643 return map(file, mode, file.length());
644 }
645
646 /**
647 * Maps a file in to memory as per
648 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
649 * using the requested {@link MapMode}.
650 *
651 * <p>Files are mapped from offset 0 to {@code size}.
652 *
653 * <p>If the mode is {@link MapMode#READ_WRITE} and the file does not exist,
654 * it will be created with the requested {@code size}. Thus this method is
655 * useful for creating memory mapped files which do not yet exist.
656 *
657 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
658 *
659 * @param file the file to map
660 * @param mode the mode to use when mapping {@code file}
661 * @return a buffer reflecting {@code file}
662 * @throws IOException if an I/O error occurs
663 *
664 * @see FileChannel#map(MapMode, long, long)
665 * @since 2.0
666 */
667 public static MappedByteBuffer map(File file, MapMode mode, long size)
668 throws FileNotFoundException, IOException {
669 checkNotNull(file);
670 checkNotNull(mode);
671
672 Closer closer = Closer.create();
673 try {
674 RandomAccessFile raf = closer.register(
675 new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw"));
676 return map(raf, mode, size);
677 } catch (Throwable e) {
678 throw closer.rethrow(e);
679 } finally {
680 closer.close();
681 }
682 }
683
684 private static MappedByteBuffer map(RandomAccessFile raf, MapMode mode,
685 long size) throws IOException {
686 Closer closer = Closer.create();
687 try {
688 FileChannel channel = closer.register(raf.getChannel());
689 return channel.map(mode, 0, size);
690 } catch (Throwable e) {
691 throw closer.rethrow(e);
692 } finally {
693 closer.close();
694 }
695 }
696
697 /**
698 * Returns the lexically cleaned form of the path name, <i>usually</i> (but
699 * not always) equivalent to the original. The following heuristics are used:
700 *
701 * <ul>
702 * <li>empty string becomes .
703 * <li>. stays as .
704 * <li>fold out ./
705 * <li>fold out ../ when possible
706 * <li>collapse multiple slashes
707 * <li>delete trailing slashes (unless the path is just "/")
708 * </ul>
709 *
710 * <p>These heuristics do not always match the behavior of the filesystem. In
711 * particular, consider the path {@code a/../b}, which {@code simplifyPath}
712 * will change to {@code b}. If {@code a} is a symlink to {@code x}, {@code
713 * a/../b} may refer to a sibling of {@code x}, rather than the sibling of
714 * {@code a} referred to by {@code b}.
715 *
716 * @since 11.0
717 */
718 public static String simplifyPath(String pathname) {
719 checkNotNull(pathname);
720 if (pathname.length() == 0) {
721 return ".";
722 }
723
724 // split the path apart
725 Iterable<String> components =
726 Splitter.on('/').omitEmptyStrings().split(pathname);
727 List<String> path = new ArrayList<String>();
728
729 // resolve ., .., and //
730 for (String component : components) {
731 if (component.equals(".")) {
732 continue;
733 } else if (component.equals("..")) {
734 if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) {
735 path.remove(path.size() - 1);
736 } else {
737 path.add("..");
738 }
739 } else {
740 path.add(component);
741 }
742 }
743
744 // put it back together
745 String result = Joiner.on('/').join(path);
746 if (pathname.charAt(0) == '/') {
747 result = "/" + result;
748 }
749
750 while (result.startsWith("/../")) {
751 result = result.substring(3);
752 }
753 if (result.equals("/..")) {
754 result = "/";
755 } else if ("".equals(result)) {
756 result = ".";
757 }
758
759 return result;
760 }
761
762 /**
763 * Returns the <a href="http://en.wikipedia.org/wiki/Filename_extension">file
764 * extension</a> for the given file name, or the empty string if the file has
765 * no extension. The result does not include the '{@code .}'.
766 *
767 * @since 11.0
768 */
769 public static String getFileExtension(String fullName) {
770 checkNotNull(fullName);
771 String fileName = new File(fullName).getName();
772 int dotIndex = fileName.lastIndexOf('.');
773 return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
774 }
775
776 /**
777 * Returns the file name without its
778 * <a href="http://en.wikipedia.org/wiki/Filename_extension">file extension</a> or path. This is
779 * similar to the {@code basename} unix command. The result does not include the '{@code .}'.
780 *
781 * @param file The name of the file to trim the extension from. This can be either a fully
782 * qualified file name (including a path) or just a file name.
783 * @return The file name without its path or extension.
784 * @since 14.0
785 */
786 public static String getNameWithoutExtension(String file) {
787 checkNotNull(file);
788 String fileName = new File(file).getName();
789 int dotIndex = fileName.lastIndexOf('.');
790 return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
791 }
792
793 /**
794 * Returns a {@link TreeTraverser} instance for {@link File} trees.
795 *
796 * <p><b>Warning:</b> {@code File} provides no support for symbolic links, and as such there is no
797 * way to ensure that a symbolic link to a directory is not followed when traversing the tree.
798 * In this case, iterables created by this traverser could contain files that are outside of the
799 * given directory or even be infinite if there is a symbolic link loop.
800 *
801 * @since 15.0
802 */
803 public static TreeTraverser<File> fileTreeTraverser() {
804 return FILE_TREE_TRAVERSER;
805 }
806
807 private static final TreeTraverser<File> FILE_TREE_TRAVERSER = new TreeTraverser<File>() {
808 @Override
809 public Iterable<File> children(File file) {
810 // check isDirectory() just because it may be faster than listFiles() on a non-directory
811 if (file.isDirectory()) {
812 File[] files = file.listFiles();
813 if (files != null) {
814 return Collections.unmodifiableList(Arrays.asList(files));
815 }
816 }
817
818 return Collections.emptyList();
819 }
820
821 @Override
822 public String toString() {
823 return "Files.fileTreeTraverser()";
824 }
825 };
826
827 /**
828 * Returns a predicate that returns the result of {@link File#isDirectory} on input files.
829 *
830 * @since 15.0
831 */
832 public static Predicate<File> isDirectory() {
833 return FilePredicate.IS_DIRECTORY;
834 }
835
836 /**
837 * Returns a predicate that returns the result of {@link File#isFile} on input files.
838 *
839 * @since 15.0
840 */
841 public static Predicate<File> isFile() {
842 return FilePredicate.IS_FILE;
843 }
844
845 private enum FilePredicate implements Predicate<File> {
846 IS_DIRECTORY {
847 @Override
848 public boolean apply(File file) {
849 return file.isDirectory();
850 }
851
852 @Override
853 public String toString() {
854 return "Files.isDirectory()";
855 }
856 },
857
858 IS_FILE {
859 @Override
860 public boolean apply(File file) {
861 return file.isFile();
862 }
863
864 @Override
865 public String toString() {
866 return "Files.isFile()";
867 }
868 };
869 }
870 }